package com.spark.core;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

@SuppressWarnings("unchecked")
public class XMap extends HashMap<Object,Object> implements Cloneable {

	private static final long serialVersionUID = 1L;

	public static final int DEFAULT_INITIAL_CAPACITY = 50;
	
	
	protected String name;
	protected String prefix;
	protected String namespaceURI;

	protected String text;

	protected XMap parent;
	protected List<XMap> childs;
	protected String source_file;
	protected Map namespace_mapping;
	
	
	
//	private void clearXML() {
//		this.xml = null;		
//	}
	
	public XMap() {
		super(DEFAULT_INITIAL_CAPACITY);
	}
	
	public XMap(String name) {
		super(DEFAULT_INITIAL_CAPACITY);
		setName(name);
	}
	
	public XMap(String name,String namespaceURI) {
		super(DEFAULT_INITIAL_CAPACITY);
		setName(name);
		setNameSpaceURI(namespaceURI);
	}
	
	public XMap(String prefix, String namespaceURI, String name) {
		super(DEFAULT_INITIAL_CAPACITY);
		setName(name);
		setPrefix(prefix);
		setNameSpaceURI(namespaceURI);
	}

	/** constructs from another composite map */
	//TODO:
	public XMap(XMap another) {
		this(another.prefix, another.namespaceURI, another.name);
		copy(another);
	}

	public XMap(String name, Map<Object,Object> map) {
		super();
		setName(name);
		putAll(map);
	}

	public void addChilds(Collection another) {
		if (another == null)
			return;
		Iterator it = another.iterator();
		while (it.hasNext()) {
			Object obj = it.next();
			if (obj instanceof XMap) {
				XMap child = new XMap((XMap) obj);
				addChild(child);
			}
		}
	}

	/**
	 * Replace a child CompositeMap with a new CompositeMap
	 * 
	 * @param child
	 *            Existing child to replace. It might be retrieved earlier by
	 *            other methods such as getChild().
	 * @param new_child
	 *            the new child. If new_child is null, then existing child will
	 *            be removed.
	 * @return if the replacement is successful, the new CompositeMap will be
	 *         returned. Otherwise the return value is null.
	 */
	public XMap replaceChild(XMap child, XMap new_child) {
		if (child == null)
			return null;
		ListIterator it = (ListIterator) getChildIterator();
		if (it == null)
			return null;
		while (it.hasNext()) {
			XMap m = (XMap) it.next();
			if (m == child) {
				if (new_child == null) {
					it.remove();
					return null;
				}
				new_child.setParent(this);
				it.set(new_child);
				return new_child;
			}
		}
		return null;
	}

	/**
	 * Replace a child CompositeMap with a new CompositeMap.
	 * 
	 * @param child_name
	 *            Name of the child CompositeMap to replace
	 * @param new_child
	 *            the new child. If new_child is null, then existing child will
	 *            be removed.
	 * @return if the replacement is successful, the new CompositeMap will be
	 *         returned. Otherwise the return value is null.
	 */
	public XMap replaceChild(String child_name, XMap new_child) {
		return replaceChild(getChild(child_name), new_child);
		/*
		 * if( child_name == null) return null; ListIterator it =
		 * (ListIterator)getChildIterator(); if( it == null) return null; while(
		 * it.hasNext()){ CompositeMap m = (CompositeMap)it.next(); if(
		 * child_name.equals(m.getName()) ){ if( new_child ==null){ return null;
		 * it.remove(); } else{ new_child.setParent(this); it.set(new_child);
		 * return new_child; } return null; } } return null;
		 */
	}

	public XMap copy(XMap another) {
		// clear();
		putAll(another);
		addChilds(another.childs);
		this.text = another.text;
		this.source_file = another.source_file;
		return this;
	}

	/**
	 * set then name of this CompositeMap
	 * 
	 * @param _name
	 *            new name
	 */
	public void setName(String _name) {
		/*
		 * if( _name != null) name = _name.toLowerCase();
		 */
		name = _name;
	}

	/**
	 * set namespace uri
	 * 
	 * @param _uri
	 *            new namespace uri
	 */
	public void setNameSpaceURI(String _uri) {
		if (_uri != null) {
			if (_uri.length() == 0)
				_uri = null;
		}
		this.namespaceURI = _uri;
	}

	/**
	 * set prefix string
	 * 
	 * @param _p
	 *            new prefix
	 */
	public void setPrefix(String _p) {
		if (_p != null) {
			if (_p.length() == 0)
				_p = null;
		}
		this.prefix = _p;
	}

	/**
	 * set namespace uri and prefix
	 * 
	 * @param _prefix
	 *            prefix string
	 * @param _uri
	 *            namespace uri
	 */
	public void setNameSpace(String _prefix, String _uri) {
		setPrefix(_prefix);
		setNameSpaceURI(_uri);
	}

	/**
	 * get name of this CompositeMap
	 * 
	 * @return name of this CompositeMap
	 */
	public String getName() {
		return name;
	}

	/**
	 * get prefix string
	 * 
	 * @return prefix of this CompositeMap
	 */
	public String getPrefix() {
		return prefix;
	}

	/**
	 * get namespace uri
	 * 
	 * @return namespace uri of this CompositeMap
	 */
	public String getNamespaceURI() {
		return namespaceURI;
	}

	/**
	 * get raw name, which is equals to prefix+":"+name
	 * 
	 * @return raw name, if prefix is null this is equals to name
	 */
	public String getRawName() {
		if (prefix == null)
			return name;
		else
			return prefix + ':' + name;
	}

	/** gets text ( CDATA section in XML document ) */
	public String getText() {
		return text;
	}

	/** sets text ( CDATA section in XML document ) */
	public void setText(String t) {
		text = t;
	}

	public void setParent(XMap p) {
		parent = p;
	}

	public XMap getParent() {
		return parent;
	}

	public XMap getRoot() {
		XMap map = parent;
		while (map != null) {
			if (map.getParent() == null)
				return map;
			else
				map = map.getParent();
		}
		return this;
	}

	public Object put(Object key, Object value) {
		if (value != null)
			if (value instanceof XMap) {
				((XMap) value).setParent(this);
			}
		/*
		 * if( key != null && key instanceof String) key =
		 * ((String)key).toLowerCase();
		 */
		return super.put(key, value);
	}

	/*
	 * 
	 * public Object get( Object key){ if( key != null && key instanceof String)
	 * key = ((String)key).toLowerCase(); return super.get(key); }
	 */

	public Object putObject(String key, Object value, char attribute_char) {
		if (key == null)
			return null;
		if (key.charAt(0) == attribute_char)
			return put(key.substring(1), value);
		else {
			if (value instanceof XMap && value != null) {
				XMap cmap = (XMap) value;
				cmap.setName(key);
				addChild(cmap);
				return value;
			} else
				return null;
		}
	}

//	public Object putObject(String key, Object value) {
//		return putObject(key, value, CompositeAccessor.DEFAULT_ATTRIB_CHAR);
//	}

	Object getObject(String key, char attribute_char) {
		if (key == null)
			return null;
		if (key.charAt(0) == attribute_char)
			return get(key.substring(1));
		else
			return getChild(key);
	}

	public void addChild(int index, XMap child) {
		child.parent = this;
		getChildsNotNull().add(index, child);
	}

	/**
	 * add an CompositeMap instance to child list
	 * 
	 * @param child
	 *            child CompositeMap to add
	 */
	public void addChild(XMap child) {
		child.parent = this;
		getChildsNotNull().add(child);
	}

	/**
	 * Remove a child CompositeMap from children list
	 * 
	 * @param child
	 *            Child CompositeMap to remove
	 * @return true if remove is success
	 */
	public boolean removeChild(XMap child) {
		boolean removed = false;
		if (childs != null) {
			ListIterator it = childs.listIterator();
			while (it.hasNext()) {
				Object obj = it.next();
				if (obj == child) {
					it.remove();
					removed = true;
					break;
				}
			}
			if (removed)
				child.setParent(null);
		}
		return removed;
	}

	/**
	 * create an child CompositeMap with specified name and namespace
	 * 
	 * @param prefix
	 *            prefix string
	 * @param uri
	 *            namespace uri
	 * @param name
	 *            name of child
	 * @return new child CompositeMap
	 * @see CompositeMap(String, String,String)
	 */
	public XMap createChild(String prefix, String uri, String name) {
		XMap child = new XMap(prefix, uri, name);
		addChild(child);
		return child;
	}

	public XMap createChild(String name) {
		return createChild(null, null, name);
	}

//	public TreeMap createChildByTag(String access_tag) {
//		return CompositeAccessor.defaultInstance().createChild(this, access_tag);
//	}

	/**
	 * get an child CompositeMap which is euqal with parameter
	 * 
	 * @param child
	 *            CompositeMap to compare
	 * @return child CompositeMap found or null if not found
	 */
	public XMap getChild(XMap child) {
		if (childs == null)
			return null;

		Iterator it = childs.iterator();
		while (it.hasNext()) {
			XMap node = (XMap) it.next();
			if (node.equals(child))
				return node;
		}

		return null;
	}

	/**
	 * get a child CompositeMap with specified name
	 * 
	 * @param name
	 *            name of CompositeMap to find
	 * @return child CompositeMap found or null
	 */
	public XMap getChild(String name) {
		if (childs == null)
			return null;

		Iterator it = childs.iterator();
		while (it.hasNext()) {
			XMap node = (XMap) it.next();
			String nm = node.getName();
			if (nm != null)
				if (nm.equals(name))
					return node;
		}
		return null;
	}

	public XMap getChildByAttrib(Object attrib_key, Object attrib_value) {
		return getChildByAttrib(null, attrib_key, attrib_value);
	}

	public XMap getChildByAttrib(String element_name, Object attrib_key,
			Object attrib_value) {
		if (attrib_key == null)
			return null;
		Iterator it = getChildIterator();
		if (it == null)
			return null;
		while (it.hasNext()) {
			XMap item = (XMap) it.next();
			if (element_name != null)
				if (!element_name.equals(item.getName()))
					continue;
			Object vl = item.get(attrib_key);
			if (vl == null) {
				if (attrib_value == null)
					return item;
			} else if (vl.equals(attrib_value))
				return item;
		}
		return null;
	}

	/**
	 * get list of childs
	 * 
	 * @return List of childs or null if no childs
	 */
	public List<XMap> getChilds() {
		return childs;
	}

	/**
	 * same as {@link getChilds()} except that if child list is null, a empty
	 * list is created thus this method will never return null
	 * 
	 * @return child list
	 */
	public List getChildsNotNull() {
		if (childs == null)
			childs = new LinkedList();
		return childs;
	}

	/**
	 * get Iterator object of child list
	 * 
	 * @return child Iterator, or null if child list is null
	 */
	public Iterator<XMap> getChildIterator() {
		if (childs == null)
			return null;
		return childs.iterator();
	}

//	public Object getObject(String key) {
//		return CompositeAccessor.defaultInstance().get(this, key);
//	}
//
//	public boolean putObject(String key, Object value, boolean create) {
//		return CompositeAccessor.defaultInstance().put(this, key, value, create);
//	}

	/**
	 * get a string parameter. map.put("sql", "select * from employee where
	 * employee_id = ${@employee_id}"); map.put("employee_id", "3"); String
	 * sql_stmt = map.getParameter("sql", true); // sql_stmt == "select * from
	 * employee where employee_id = 3"
	 */

//	public String getParameter(String key, boolean parse_param) {
//		Object obj = get(key);
//		if (obj == null)
//			return null;
//		if (obj instanceof String) {
//			if (parse_param)
//				return TextParser.parse((String) obj, this);
//			else
//				return (String) obj;
//		} else
//			return obj.toString();
//	}

	/**
	 * override Object.toString()
	 */
	/*
	 * public String toString(){
	 * 
	 * StringBuffer rst =new StringBuffer(); String nm = getRawName(); if( nm !=
	 * null) rst.append('"').append(nm).append('"'); rst.append(
	 * super.toString()).append(" "); if( childs != null){ rst.append("->
	 * ").append( childs.toString()).append(" "); } return rst.toString(); }
	 */
	
	public String toXML() {
		return XMLOutputter.defaultInstance().toXML(this);
	}

//	int iterateChild(IterationHandle handle, boolean root_first) {
//		int result = IterationHandle.IT_CONTINUE;
//		if (childs == null)
//			return result;
//		ListIterator it = childs.listIterator();
//		while (it.hasNext()) {
//			TreeMap child = (TreeMap) it.next();
//			result = child.iterate(handle, root_first);
//			if (result == IterationHandle.IT_BREAK)
//				return result;
//			else if (result == IterationHandle.IT_REMOVE) {
//				it.remove();
//				result = IterationHandle.IT_CONTINUE;
//				// return IterationHandle.IT_CONTINUE;
//			}
//		}
//		return result;
//	}
//
//	public int iterate(IterationHandle handle, boolean root_first) {
//		int result;
//		if (root_first) {
//			result = handle.process(this);
//			if (result == IterationHandle.IT_CONTINUE)
//				result = iterateChild(handle, root_first);
//			return result;
//		} else {
//			result = iterateChild(handle, root_first);
//			if (result != IterationHandle.IT_BREAK)
//				handle.process(this);
//			return result;
//		}
//	}

	/*
	 * public void writeExternal(ObjectOutput out) throws IOException {
	 * out.writeObject(name); out.writeObject(namespace_uri);
	 * out.writeObject(prefix); out.writeObject(childs);
	 * System.out.println("writeExternal:"+childs);
	 *  }
	 * 
	 * public void readExternal(ObjectInput in) throws IOException,
	 * ClassNotFoundException { name = (String)in.readObject(); namespace_uri =
	 * (String)in.readObject(); prefix = (String)in.readObject(); childs =
	 * (List)in.readObject(); System.out.println("readExternal:"+childs); }
	 */
	/**
	 * @see java.util.Map#clear()
	 */
	public void clear() {
		super.clear();
		if (childs != null) {
			childs.clear();
		}
		name = null;
		text = null;
		namespaceURI = null;
		prefix = null;
		childs = null;
		parent = null;
		source_file = null;
	}

//	public QualifiedName getQName() {
//		return new QualifiedName(namespaceURI, name);
//	}

	/**
	 * create a new CompositeMap and copy content to it
	 */
	public Object clone() {
		XMap m = (XMap) super.clone();
		if (childs != null) {
			m.childs = new LinkedList();
			Iterator it = childs.iterator();
			while (it.hasNext()) {
				XMap child = (XMap) it.next();
				XMap new_child = (XMap) child.clone();
				new_child.setParent(m);
				m.childs.add(new_child);
			}
		}
		return m;
	}

	public String getSourceFile() {
		return source_file;
	}

	/** source file path where this CompositeMap is parsed from */
	public void setSourceFile(String file) {
		source_file = file;
	}

	/**
	 * get a Map containing namespace mapping, with namespace url as key,
	 * namespace prefix as value
	 */
	public Map getNamespaceMapping() {
		return namespace_mapping;
	}

	public void setNamespaceMapping(Map mapping) {
		namespace_mapping = mapping;
	}

	/*
	 * // Location in source file public Locator getLocator() { return locator; }
	 * 
	 * public void setLocator(Locator locator) { this.locator = locator; }
	 */

	public String getString(Object key) {
		Object obj = get(key);
		return obj == null ? null : obj.toString();
	}

	public String getString(Object key, String default_value) {
		String str = getString(key);
		return str == null ? default_value : str;
	}

	public void putString(Object key, String value) {
		put(key, value);
	}

	public Boolean getBoolean(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Boolean)
			return (Boolean) obj;
		if (obj instanceof String) {
			Boolean bl = Boolean.valueOf((String) obj);
			put(key, bl);
			return bl;
		}
		return (Boolean) null;
	}

	public boolean getBoolean(Object key, boolean default_value) {
		Boolean bl = getBoolean(key);
		return bl == null ? default_value : bl.booleanValue();
	}

	public void putBoolean(Object key, boolean value) {
		put(key, Boolean.valueOf(value));
	}

	public Integer getInt(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Integer)
			return (Integer) obj;
		if (obj instanceof Number)
			return new Integer(((Number) obj).intValue());
		if (obj instanceof String) {
			Integer parsed_object = new Integer((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;
	}

	public int getInt(Object key, int default_value) {
		Integer parsed_object = getInt(key);
		return parsed_object == null ? default_value
				: ((Integer) parsed_object).intValue();
	}

	public void putInt(Object key, int value) {
		put(key, new Integer(value));
	}

	public Long getLong(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Long)
			return (Long) obj;
		if (obj instanceof Number)
			return new Long(((Number) obj).longValue());
		if (obj instanceof String) {
			Long parsed_object = new Long((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;
	}

	public long getLong(Object key, long default_value) {
		Long parsed_object = getLong(key);
		return parsed_object == null ? default_value : ((Long) parsed_object)
				.longValue();
	}

	public void putLong(Object key, long value) {
		put(key, new Long(value));
	}

	public Short getShort(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Short)
			return (Short) obj;
		if (obj instanceof Number)
			return new Short(((Number) obj).shortValue());
		if (obj instanceof String) {
			Short parsed_object = new Short((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;

	}

	public short getShort(Object key, short default_value) {
		Short parsed_object = getShort(key);
		return parsed_object == null ? default_value : ((Short) parsed_object)
				.shortValue();
	}

	public void putShort(Object key, short value) {
		put(key, new Short(value));
	}

	public Double getDouble(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Double)
			return (Double) obj;
		if (obj instanceof Number)
			return new Double(((Number) obj).doubleValue());
		if (obj instanceof String) {
			Double parsed_object = new Double((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;

	}

	public double getDouble(Object key, double default_value) {
		Double parsed_object = getDouble(key);
		return parsed_object == null ? default_value : ((Double) parsed_object)
				.doubleValue();
	}

	public void putDouble(Object key, double value) {
		put(key, new Double(value));
	}

	public Float getFloat(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Float)
			return (Float) obj;
		if (obj instanceof Number)
			return new Float(((Number) obj).floatValue());
		if (obj instanceof String) {
			Float parsed_object = new Float((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;

	}

	public float getFloat(Object key, float default_value) {
		Float parsed_object = getFloat(key);
		return parsed_object == null ? default_value : ((Float) parsed_object)
				.floatValue();
	}

	public void putFloat(Object key, float value) {
		put(key, new Float(value));
	}

	public Byte getByte(Object key) {
		Object obj = get(key);
		if (obj == null)
			return null;
		if (obj instanceof Byte)
			return (Byte) obj;
		if (obj instanceof Number)
			return new Byte(((Number) obj).byteValue());
		if (obj instanceof String) {
			Byte parsed_object = new Byte((String) obj);
			put(key, parsed_object);
			return parsed_object;
		}
		return null;

	}

	public byte getByte(Object key, byte default_value) {
		Byte parsed_object = getByte(key);
		return parsed_object == null ? default_value : ((Byte) parsed_object)
				.byteValue();
	}
	
	public int hashCode() {
    	return System.identityHashCode(this);
    }

}
